﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Krile.Kernel.Bridges;
using Krile.Data;
using Krile.Kernel.Data;
using System.ComponentModel;
using Krile.Kernel.WindowsEx;
using Azrael.Kernel.Base;

namespace Krile.Cores
{
    public class StatusManager : IStatusManager
    {
        public event Krile.Kernel.Data.Common.StatusRegistedCallback OnStatusRegistered;

        public event Krile.Kernel.Data.Common.StatusRegistedCallback OnStatusRemoved;

        internal delegate void XStatusRegisteredCallback(IEnumerable<XStatus> xstatuses);

        internal event XStatusRegisteredCallback OnXStatusRegistered;

        internal event XStatusRegisteredCallback OnXStatusRemoved;

        internal event XStatusRegisteredCallback OnXStatusUpdated;

        /// <summary>
        /// Main storage of status array
        /// </summary>
        private SafeDictionary<ulong, XStatus> statusStorage;

        /// <summary>
        /// Preset search data list
        /// </summary>
        private SafeDictionary<string, Type> presetSearchDataList;

        private static object uidLocker = new object();
        private ulong uidSource = 1;

        internal StatusManager()
        {
            statusStorage = new SafeDictionary<ulong, XStatus>();
            presetSearchDataList = new SafeDictionary<string, Type>();
        }

        internal XStatus[] GetAllStatuses()
        {
            return statusStorage.LockOperate<XStatus[]>(() =>
            {
                return statusStorage.Values.ToArray();
            });
        }

        internal int GetStatusesCount()
        {
            return statusStorage.Count;
        }

        internal SearchData[] GetAllSearchData()
        {
            return presetSearchDataList.LockOperate<SearchData[]>(() =>
            {
                return (from p in presetSearchDataList.Values
                        select ((SearchData)Activator.CreateInstance(p))).ToArray();
            });
        }

        public Status[] ExtractStatuses(SearchDataBase sdb)
        {
            var array = ExtractXStatuses(sdb);
            List<Status> ova = new List<Status>();
            foreach (var ai in array)
                ova.Add(ai.BaseStatus);
            return ova.ToArray();
        }

        internal XStatus[] ExtractXStatuses(SearchDataBase sdb)
        {
            return statusStorage.LockOperate<XStatus[]>(() =>
                {
                    return (from s in statusStorage.Values
                            where sdb == null || sdb.IsMatched(s.BaseStatus)
                            select s).ToArray();
                });
        }

        public ulong GetNextUID()
        {
            lock (uidLocker)
                return uidSource++;
        }

        public Status GetStatus(ulong uid)
        {
            if (statusStorage.ContainsKey(uid))
                return statusStorage[uid].BaseStatus;
            else
                return null;
        }

        public bool IdExists(ulong uid)
        {
            return statusStorage.ContainsKey(uid);
        }

        public SearchData LookupSearchData(string sdid)
        {
            if (presetSearchDataList.ContainsKey(sdid))
                return (SearchData)Activator.CreateInstance(presetSearchDataList[sdid]);
            else
                return null;
        }

        public void RegistNewSearchData(Type searchDataType)
        {
            if (!searchDataType.IsSubclassOf(typeof(SearchData)))
                throw new KrilePluginExecutionException("Argument isn't inherit \"Krile.Kernel.Data.SearchData\".");
            SearchData sd = (SearchData)Activator.CreateInstance(searchDataType);
            if (sd.Identification == null)
                throw new KrilePluginExecutionException("Argument's id is not valid.You can't use null for id.");
            if (presetSearchDataList.ContainsKey(sd.Identification))
                throw new KrilePluginExecutionException("Argument's id:\"" + sd.Identification + "\" is already exists in register.");
            presetSearchDataList.Add(sd.Identification, searchDataType);
        }

        public void RegistStatus(IEnumerable<Status> statuses)
        {
            RegistStatus(statuses, true);
        }

        static object trimLock = new object();
        ulong trimCountStart = 1;
        public void RegistStatus(IEnumerable<Status> statuses, bool isNew)
        {
            if (statuses == null) return;
            List<XStatus> regXStatus = new List<XStatus>(statuses.Count());
            foreach (var status in statuses)
            {
                if (Core.Config.Timeline.NGSDClusters != null &&
                    Core.Config.Timeline.NGSDClusters.SearchDatas != null &&
                    Core.Config.Timeline.NGSDClusters.SearchDatas.Length > 0 &&
                    Core.Config.Timeline.NGSDClusters.IsMatched(status))
                {
                    continue;
                }
                if (IdExists(status.UID))
                    throw new KrileManagementException("Status which has id:" + status.UID.ToString() + " is already exists in status storage.");
                if (status.ParentAgent.Status.AutoCreateFavOperationTriggers || status.ParentAgent.Status.AutoCreateReplyOperationTriggers || status.ParentAgent.Status.AutoCreateDeleteOperationTriggers)
                {
                    List<Krile.Kernel.Control.Surface.CommandButton> cmdbtns = new List<Krile.Kernel.Control.Surface.CommandButton>();
                    if (status.Commands != null)
                        cmdbtns.AddRange(status.Commands);
                    if (status.ParentAgent.Status.AutoCreateFavOperationTriggers &&
                        (Core.Config.Timeline.AllowFavMyself || !status.ParentAgent.IsMasterId(status.User.Id)) &&
                        !status.Unfavorable)
                        cmdbtns.Add(Data.DefaultCommandButtons.GetFavButton(status));
                    if (status.ParentAgent.Status.AutoCreateReplyOperationTriggers &&
                        !status.Unreplyable)
                        cmdbtns.Add(Data.DefaultCommandButtons.GetReplyButton(status));
                    if (status.ParentAgent.Status.AutoCreateDeleteOperationTriggers &&
                        status.Destroyable)
                        cmdbtns.Add(Data.DefaultCommandButtons.GetDeleteButton(status));
                    status.Commands = cmdbtns.ToArray();
                }
                XStatus n = new XStatus(status);
                n.Unread = isNew;
                statusStorage.Add(status.UID, n);
                regXStatus.Add(n);
            }

            if (Core.Config.Timeline.StatusesKeepMaximum > 0 && statusStorage.Count > Core.Config.Timeline.StatusesKeepMaximum)
            {
                lock (trimLock)
                {
                    if (Core.Config.Timeline.StatusesKeepMaximum > 0 && statusStorage.Count > Core.Config.Timeline.StatusesKeepMaximum)
                    {
                        long trimCount = statusStorage.Count - Core.Config.Timeline.StatusesKeepMaximum;
                        var trims = new ulong[trimCount];
                        ulong j = trimCountStart;
                        for (int i = 0; i < trimCount; i++)
                        {
                            while (!IdExists(j))
                                j++;
                            trims[i] = j++;
                        }
                        trimCountStart = ++j;
                        RemoveStatus(trims);
                    }
                }
            }

            Core.NotificationManager.BeginSleep();
            if (OnStatusRegistered != null)
                OnStatusRegistered.Invoke(statuses);
            if (OnXStatusRegistered != null)
                OnXStatusRegistered.Invoke(regXStatus.ToArray());
            Core.NotificationManager.EndSleep();
        }

        public void RemoveStatus(ulong[] rmuids)
        {
            List<XStatus> delXStatus = new List<XStatus>();
            List<Status> delStatus = new List<Status>();
            statusStorage.LockOperate(() =>
            {
                foreach (var uid in rmuids)
                {
                    if (!IdExists(uid))
                        throw new KrileManagementException("Status which has id:" + uid.ToString() + " isn't exists in status storage.");
                    XStatus xs = statusStorage[uid];
                    delStatus.Add(xs.BaseStatus);
                    delXStatus.Add(xs);
                    statusStorage.RemoveUnsafe(uid);
                }
            });

            if (OnStatusRemoved != null)
                OnStatusRemoved.Invoke(delStatus.ToArray());
            if (OnXStatusRemoved != null)
                OnXStatusRemoved.Invoke(delXStatus.ToArray());
        }

        internal void RemoveStatusWithOnSvr(Status status)
        {
            if (status != null)
            {
                if (status.ParentAgent.Status.Destroiable &&
                    status.Destroyable)
                {
                    var ret = MessageBoxEx.Show(
                        Lang.Msg.StatusRemove_External_Context,
                        Lang.Msg.StatusRemove_External,
                        Lang.Msg.StatusRemove_External_Caption,
                        MessageBoxEx.MessageBoxExButtons.YesNoCancel,
                        MessageBoxEx.MessageBoxExIcon.Question);
                    if (ret == MessageBoxEx.DialogResultEx.Cancel)
                        return;
                    else if (ret == MessageBoxEx.DialogResultEx.Yes)
                    {
                        try
                        {
                            status.ParentAgent.Status.Destroy(status.UID);
                        }
                        catch (Exception ex)
                        {
                            MessageBoxEx.Show("Remove error", ex.ToString(), "Remove failed", MessageBoxEx.MessageBoxExButtons.Close, MessageBoxEx.MessageBoxExIcon.Error);
                            return;
                        }
                    }
                }
                else
                {
                    if (MessageBoxEx.Show(
                        Lang.Msg.StatusRemove_Internal_Context,
                        Lang.Msg.StatusRemove_Internal,
                        Lang.Msg.StatusRemove_External_Caption,
                        MessageBoxEx.MessageBoxExButtons.YesNo,
                         MessageBoxEx.MessageBoxExIcon.Question) == MessageBoxEx.DialogResultEx.No)
                        return;
                }
                Core.StatusManager.RemoveStatus(new[] { status.UID });
            }
        }

        internal static class StatusSorter
        {
            internal static int StatusSortComparison(XStatus x, XStatus y)
            {
                if (x == y || (x == null && y == null))
                    return 0;
                if (x.BaseStatus == null || y.BaseStatus == null)
                    throw new KrileStrictCheckException("Invalid status has registered.");
                if (x == null)
                    return -1;
                if (y == null)
                    return 1;
                if (x.BaseStatus.UID == y.BaseStatus.UID)
                    return 0;
                int vector = x.BaseStatus.CreatedAt.CompareTo(y.BaseStatus.CreatedAt);
                if (vector == 0)
                    vector = (int)x.BaseStatus.UID - (int)y.BaseStatus.UID;

                return Core.Config.Timeline.ListBottomNewer ? vector : -vector;
            }

            public static event Action StatusSortComparisonUpdated;
            public static void StatusSortComparisonUpdateInvoke()
            {
                if (StatusSortComparisonUpdated != null)
                    StatusSortComparisonUpdated.Invoke();
            }
        }

        public void UpdateStatus(ulong[] uids)
        {
            List<XStatus> updated = new List<XStatus>();
            foreach (var uid in uids)
            {
                if (!IdExists(uid))
                    throw new KrileManagementException("Status which has id:" + uid.ToString() + " isn't exists in status storage.");
                XStatus xs = statusStorage[uid];
                updated.Add(xs);
            }
            UpdateStatus(updated);
        }

        internal void UpdateStatus(IEnumerable<XStatus> xses)
        {
            if(OnXStatusUpdated != null)
            OnXStatusUpdated.Invoke(xses);
        }
    }
}
